<?php

namespace app\service\token;

use think\Cache;
class TokenStore
{
    protected $cache;
    function __construct(?Cache $cache)
    {
        $this->cache =  $cache;        
    }

    /**
     * 生成token 
     * @param string $user_id 用户id
     * @param string $sub 主题
     * @param string $expire_time 多少秒后过期
     * @param boolean $is_store 是否存储到缓存
     * @param array $payload 有效负载
     * @return Token
     */
    function generateToken($user_id,$sub,$expire_time,$is_store = false,$payload = [])
    {
        $header = [
            'token'=>$this->createId($user_id),
            'typ'=>'jwt',
            'aud'=>$user_id,
            'exp'=>$expire_time,
            'sub'=>$sub,
            'iat'=>time(),
            's'=>$is_store?1:0
        ];
        $token =  new Token($header,$payload,$this);
        if($is_store){
            $this->store($token);
        }
        return $token;
    }

    function getToken($tokenStr)
    {
        $token = $this->decodeToken($tokenStr);
        if(is_null($token)){
            return null;
        }
        $token = new Token($token[0],$token[1],$this);
        if($token->header['exp']+$token->header['iat'] < time()){
            return null;
        }
        if($token->header['s'] ==true ){
            if(!$this->storeHas($token)){
                return null;
            }
        }
        return $token;
    }
    function getTokenBySub($tokenStr,$sub)
    {
        $token = $this->getToken($tokenStr);
        if(!!$token && $token->header['sub'] == $sub){
            return $token;
        }
        return null;
    }

    /**
     * 对token进行缓存
     * @param Token  $token
     * @return bool
     */
    function store($token)
    {
        $exp = $token->header['exp']-$token->header['iat'];
        if($exp < 1){
            return false;
        }
        $this->cache->set($this->getKey($token->header['sub'],$token->header['aud'],$token->header['jti']),$token->header['aud'],$exp);
        return true;
    }

    /**
     * 判断缓存是否存在
     * @param Token  $token
     * @return bool
     */
    function storeHas($token)
    {
        $sub = $token->header['sub'];
        $aud = $token->header['aud'];
        $jti = $token->header['jti'];
        $key = $this->getKey($sub,$aud,$jti);
        return $this->cache->has($key);
    }

    /**
     * @param [type] $token
     * @param [type] $time 多少秒后失效
     * @return Token
     */
    function setExpireTime($token,$time)
    {
        $time = time()+$time;
        $header = $token->header['header'];
        $header['exp'] = $time;
        $newToken = new Token($header,$this->payload,$this);
        if($newToken->header['s']){
            $this->store($newToken);
        }
        return $newToken;
    }

    function createId($user_id)
    {
        return md5(time().$user_id.env('SAFE_SECURITY'));
    }


    function encodeToken($data,$payload)
    {
        $base_data = base64_safe_encode(json_encode($data));
        $base_payload = base64_safe_encode(json_encode($payload));
        $sign = $this->sign($base_data,$base_payload);
        return "$sign.$base_data.$base_payload";
    }
    /**
     *
     * @param [type] $token
     * @return array|null
     */
    function decodeToken($token)
    {
        $tokens = explode('.',$token);
        if(count($tokens) != 3){
            return null;
        }
        if($tokens[0] != $this->sign($tokens[1],$tokens[2])){
            return null;
        }
        return [
            json_decode(base64_safe_decode($tokens[1]),true),
            json_decode(base64_safe_decode($tokens[2]),true),
        ];
    }

    protected function sign($header,$payload)
    {
        return md5($header.$payload.env('SAFE_SECURITY'));
    }

    protected function getKey($sub,$aud,$jti)
    {
        return str_replace('\\','.',self::class)."::$sub.$aud.$jti";
    }

}